﻿namespace QRCodeBasicsWPF
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.Kinect;
    using ZXing.Kinect;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Windows.Controls;

    /// <summary>
    /// Interaction logic for MainWindow
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private const int DECODE_EVERY_N_FRAMES = 30;
        private KinectDecoder _kinectDecoder = new KinectDecoder(DECODE_EVERY_N_FRAMES);

        /// <summary>
        /// Size of the RGB pixel in the bitmap
        /// </summary>
        private readonly int bytesPerPixel = (PixelFormats.Bgr32.BitsPerPixel + 7) / 8;

        /// <summary>
        /// Active Kinect sensor
        /// </summary>
        private KinectSensor kinectSensor = null;

        /// <summary>
        /// Reader for color/skeleton frames
        /// </summary>
        private MultiSourceFrameReader reader = null;

        /// <summary>
        /// Bitmap to display
        /// </summary>
        private WriteableBitmap bitmap = null;

        /// <summary>
        /// Intermediate storage for receiving frame data from the sensor
        /// </summary>
        private byte[] pixels = null;

        /// <summary>
        /// Array for the bodies
        /// </summary>
        private Body[] bodies = null;

        /// <summary>
        /// Current status text to display
        /// </summary>
        private string statusText = null;

        /// <summary>
        /// Initializes a new instance of the MainWindow class.
        /// </summary>
        public MainWindow()
        {
            // for Alpha, one sensor is supported
            this.kinectSensor = KinectSensor.GetDefault();

            if (this.kinectSensor != null)
            {
                // open the sensor
                this.kinectSensor.Open();

                _kinectDecoder.CoordinateMapper = kinectSensor.CoordinateMapper;

                FrameDescription frameDescription = this.kinectSensor.ColorFrameSource.FrameDescription;

                // open the reader for the color frames
                this.reader = this.kinectSensor.OpenMultiSourceFrameReader(FrameSourceTypes.Body | FrameSourceTypes.Color);

                this.bodies = new Body[this.kinectSensor.BodyFrameSource.BodyCount];

                // allocate space to put the pixels being received
                this.pixels = new byte[frameDescription.Width * frameDescription.Height * this.bytesPerPixel];

                // create the bitmap to display
                this.bitmap = new WriteableBitmap(frameDescription.Width, frameDescription.Height, 96.0, 96.0, PixelFormats.Bgr32, null);

                // set the status text
                this.StatusText = Properties.Resources.InitializingStatusTextFormat;

            }
            else
            {
                // on failure, set the status text
                this.StatusText = Properties.Resources.NoSensorStatusText;
            }

            // use the window object as the view model in this simple example
            this.DataContext = this;

            // initialize the components (controls) of the window
            this.InitializeComponent();
        }

        /// <summary>
        /// INotifyPropertyChangedPropertyChanged event to allow window controls to bind to changeable data
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets the bitmap to display
        /// </summary>
        public ImageSource ImageSource
        {
            get
            {
                return this.bitmap;
            }
        }

        /// <summary>
        /// Gets or sets the current status text to display
        /// </summary>
        public string StatusText
        {
            get
            {
                return this.statusText;
            }

            set
            {
                if (this.statusText != value)
                {
                    this.statusText = value;

                    // notify any bound elements that the text has changed
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("StatusText"));
                    }
                }
            }
        }

        /// <summary>
        /// Execute start up tasks
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            if (this.reader != null)
            {
                this.reader.MultiSourceFrameArrived += this.Reader_MultiSourceFrameArrived;
            }
        }

        /// <summary>
        /// Execute shutdown tasks
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            if (this.reader != null)
            {
                this.reader.Dispose();
                this.reader = null;
            }
            if (this.reader != null)
            {
                this.reader.Dispose();
                this.reader = null;
            }

            if (this.kinectSensor != null)
            {
                this.kinectSensor.Close();
                this.kinectSensor = null;
            }
        }

        /// <summary>
        /// Handles the color frame data arriving from the sensor
        /// </summary>
        /// <param name="sender">object sending the event</param>
        /// <param name="e">event arguments</param>
        private void Reader_MultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
        {
            MultiSourceFrameReference frameReference = e.FrameReference;

            MultiSourceFrame multiSourceFrame = null;
            BodyFrame bodyFrame = null;
            ColorFrame colorFrame = null;
            try
            {
                multiSourceFrame = frameReference.AcquireFrame();

                if (multiSourceFrame != null)
                {
                    BodyFrameReference bodyFrameReference = multiSourceFrame.BodyFrameReference;
                    ColorFrameReference colorFrameReference = multiSourceFrame.ColorFrameReference;

                    bodyFrame = bodyFrameReference.AcquireFrame();
                    colorFrame = colorFrameReference.AcquireFrame();

                    if (colorFrame != null)
                    {
                        FrameDescription frameDescription = colorFrame.FrameDescription;

                        // verify data and write the new color frame data to the display bitmap
                        if ((frameDescription.Width == this.bitmap.PixelWidth) && (frameDescription.Height == this.bitmap.PixelHeight))
                        {
                            if (colorFrame.RawColorImageFormat == ColorImageFormat.Bgra)
                            {
                                colorFrame.CopyRawFrameDataToArray(this.pixels);
                            }
                            else
                            {
                                colorFrame.CopyConvertedFrameDataToArray(this.pixels, ColorImageFormat.Bgra);
                            }

                            this.bitmap.WritePixels(
                                new Int32Rect(0, 0, frameDescription.Width, frameDescription.Height),
                                this.pixels,
                                frameDescription.Width * this.bytesPerPixel,
                                0);

                            DrawTarget(_kinectDecoder.Zone);
                            Body selectedBody = null;
                            if (bodyFrame != null)
                            {
                                bodyFrame.GetAndRefreshBodyData(bodies);
                                selectedBody = bodies.Where(b => b != null && b.IsTracked).OrderBy(b => b.Joints[JointType.SpineMid].Position.Z).FirstOrDefault();
                            }
                            _kinectDecoder.Decode(colorFrame.FrameDescription, this.pixels, selectedBody);

                            if (_kinectDecoder.LastGoodResult != null)
                            {
                                this.StatusText = _kinectDecoder.LastGoodResult.Text + "  "
                                    + _kinectDecoder.LastGoodResult.ResultPoints[0].X + " x " + _kinectDecoder.LastGoodResult.ResultPoints[0].Y;
                                DrawQRCodeMarker(this.bitmap, _kinectDecoder.LastGoodResult.ResultPoints, _kinectDecoder.Zone);
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                // ignore if the frame is no longer available
            }
            finally
            {
                if (bodyFrame != null)
                {
                    bodyFrame.Dispose();
                    bodyFrame = null;
                }
                if (colorFrame != null)
                {
                    colorFrame.Dispose();
                    colorFrame = null;
                }

                if (multiSourceFrame != null)
                {
                    multiSourceFrame = null;
                }
            }
        }

        private void DrawQRCodeMarker(WriteableBitmap bitmap, ZXing.ResultPoint[] resultPoints, Rectangle zone)
        {
            using (Bitmap bmp = new Bitmap(
                bitmap.PixelWidth, this.bitmap.PixelHeight,
                bitmap.BackBufferStride,
                System.Drawing.Imaging.PixelFormat.Format32bppPArgb,
                bitmap.BackBuffer))
            {
                using (Graphics graphics = Graphics.FromImage(bmp))
                {
                    int qrcodeMaxX = zone.X + (int)Math.Max(resultPoints[0].X, Math.Max(resultPoints[1].X, resultPoints[2].X));
                    int qrcodeMinX = zone.X + (int)Math.Min(resultPoints[0].X, Math.Min(resultPoints[1].X, resultPoints[2].X));
                    int qrcodeMaxY = zone.Y + (int)Math.Max(resultPoints[0].Y, Math.Max(resultPoints[1].Y, resultPoints[2].Y));
                    int qrcodeMinY = zone.Y + (int)Math.Min(resultPoints[0].Y, Math.Min(resultPoints[1].Y, resultPoints[2].Y));
                    graphics.FillRectangle(new SolidBrush(System.Drawing.Color.Green), qrcodeMinX, qrcodeMinY, qrcodeMaxX - qrcodeMinX, qrcodeMaxY - qrcodeMinY);
                }
            }
        }

        private void DrawTarget(Rectangle zone)
        {
            using (Bitmap bmp = new Bitmap(
                   this.bitmap.PixelWidth, this.bitmap.PixelHeight,
                   this.bitmap.BackBufferStride,
                   System.Drawing.Imaging.PixelFormat.Format32bppPArgb,
                   this.bitmap.BackBuffer))
            {
                using (Graphics graphics = Graphics.FromImage(bmp))
                {
                    graphics.DrawRectangle(new System.Drawing.Pen(System.Drawing.Brushes.Green, 5), zone);
                }
            }
        }


        private void QRCodeMode_Checked(object sender, RoutedEventArgs e)
        {
            if (QRCodeMode_Full != null && QRCodeMode_Full.IsChecked.Value)
                _kinectDecoder.DecodingMode = KinectDecoder.DecodingModes.Full;
            if (QRCodeMode_Joint != null && QRCodeMode_Joint.IsChecked.Value)
                _kinectDecoder.DecodingMode = KinectDecoder.DecodingModes.TrackedJoints;
            if (QRCodeMode_PredefinedZone != null && QRCodeMode_PredefinedZone.IsChecked.Value) 
                _kinectDecoder.DecodingMode = KinectDecoder.DecodingModes.PredefinedZone;
        }

        private void ToggleButton_Checked(object sender, RoutedEventArgs e)
        {
            UIElement element = sender as UIElement;
            int col = Grid.GetColumn(element);
            int row = Grid.GetRow(element);
            _kinectDecoder.GeneratePredefinedZone(kinectSensor.ColorFrameSource.FrameDescription, col, 4, row, 3);
            QRCodeMode_PredefinedZone.IsChecked = true;
        }

      

        private void QRCodeSelectedJoint_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            _kinectDecoder.TrackedJoint = (JointType)QRCodeSelectedJoint.SelectedItem;
        }
    }
}
